#include <microhttpd.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#ifdef MHD_HAVE_LIBMAGIC
#include <magic.h>
#endif /* MHD_HAVE_LIBMAGIC */
#include <limits.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/types.h>

#ifdef MHD_HAVE_LIBMAGIC
#define MAGIC_HEADER_SIZE (16 * 1024)
#endif /* MHD_HAVE_LIBMAGIC */

#define FILE_NOT_FOUND_PAGE \
  "<html><head><title>File not found</title></head><body>File not found</body></html>"
static struct MHD_Response *file_not_found_response;
static struct MHD_Response *request_refused_response;

#ifdef MHD_HAVE_LIBMAGIC
static magic_t magic;
#endif /* MHD_HAVE_LIBMAGIC */
struct ts_httpd {
  void *user_data;
  const char *root_path;
  void *callback;
};
typedef char * (*http_cal)(const char *, const char *, const char *, void *);
static enum MHD_Result generate_page (void *cls,
    struct MHD_Connection *connection, const char *url, const char *method,
    const char *version, const char *upload_data, size_t *upload_data_size,
    void **ptr)
{
  struct MHD_Response *response;
  enum MHD_Result ret;
  int fd, buf_size = 10;
  struct stat buf;
  (void) version;           /* Unused. Silent compiler warning. */

  printf("[++++]F%s L%d method:%s, url:%s \n", __FILE__, __LINE__, method, url);
  printf("\tL%d data:%s[%zu]\n", __LINE__, upload_data, *upload_data_size);
  http_cal cal = cls; 
  char *res = NULL;
  if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) {
    if (!(*ptr)) {
      *ptr = calloc(1, buf_size);
      return MHD_YES;
    }
    if (0 != *upload_data_size) {
      if (*upload_data_size > buf_size)
        *ptr = calloc(1, *upload_data_size);
      strcpy(*ptr, upload_data);
      *upload_data_size = 0;
      return MHD_YES;//1
    } else {
      res = cal(method, url, *ptr, cls);
      free(*ptr);
    }
  } else if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) {
    res = cal(method, url, NULL, cls);
  }
  if (res) {
    response = MHD_create_response_from_buffer(
        strlen(res), res, MHD_RESPMEM_MUST_COPY);
    free(res);
    if (!response) {
      printf("[ERROR]F%s L%d RESPONSE IS NULL\n", __FILE__, __LINE__);
      exit(1);
    }
    ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    MHD_destroy_response (response);
    printf("[DEBUG]F%s L%d connection:%p response:%p\n", __FILE__, __LINE__,
        connection, response);
    printf("[DEBUG]F%s L%d ret:%d\n", __FILE__, __LINE__, ret);
    return ret;
  }
  if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) {
    if (0 != strcmp (url, "/")) {
#ifdef MHD_HAVE_LIBMAGIC
      char file_data[MAGIC_HEADER_SIZE];
      ssize_t got;
#endif /* MHD_HAVE_LIBMAGIC */
      const char *mime;
      if ( (0 != strcmp (method, MHD_HTTP_METHOD_GET)) &&
           (0 != strcmp (method, MHD_HTTP_METHOD_HEAD)) )
        return MHD_NO;    /* unexpected method (we're not polite...) */
      fd = -1;
      if ( (NULL == strstr (&url[1], "..")) && ('/' != url[1]) ) {
        fd = open (&url[1], O_RDONLY);
        if ( (-1 != fd) && ( (0 != fstat (fd, &buf)) ||
               (! S_ISREG (buf.st_mode)) ) ) {
          (void) close (fd);
          fd = -1;
        }
      }
      if (-1 == fd)
        return MHD_queue_response (connection, MHD_HTTP_NOT_FOUND,
                                   file_not_found_response);
#ifdef MHD_HAVE_LIBMAGIC
      got = read (fd, file_data, sizeof (file_data));
      (void) lseek (fd, 0, SEEK_SET);
      if (-1 != got)
        mime = magic_buffer (magic, file_data, got);
      else
#endif /* MHD_HAVE_LIBMAGIC */
      mime = NULL;
      if (NULL == (response = MHD_create_response_from_fd (buf.st_size, fd))) {
        (void) close (fd);
        return MHD_NO;
      }
      if (NULL != mime)
        (void) MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE,
                                        mime);
      ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
      if (ret == MHD_NO)
        return ret;
      MHD_destroy_response (response);
      return ret;
    }
  }
  return MHD_queue_response (connection, MHD_HTTP_FORBIDDEN,
      request_refused_response);
}
static void
request_completed_callback (void *cls,
                            struct MHD_Connection *connection,
                            void **con_cls,
                            enum MHD_RequestTerminationCode toe)
{
  char *data = *con_cls;
  free(data);
}
void *httpd_start(int port, void *user_data)
{
  struct MHD_Daemon *d;
#ifdef MHD_HAVE_LIBMAGIC
  magic = magic_open (MAGIC_MIME_TYPE);
  (void) magic_load (magic, NULL);
#endif /* MHD_HAVE_LIBMAGIC */

  d = MHD_start_daemon (
      MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD| MHD_USE_ERROR_LOG,
      port, NULL, NULL,
      &generate_page, user_data,
      MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t)(256*1024),
      MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int)(64),
      MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) (120),
      MHD_OPTION_THREAD_POOL_SIZE, (unsigned int)4,
//      MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
      MHD_OPTION_END);
  return d;
}
void httpd_stop(void *ptr)
{
  struct MHD_Daemon *d = ptr;
  MHD_stop_daemon (d);
#ifdef MHD_HAVE_LIBMAGIC
  magic_close (magic);
#endif /* MHD_HAVE_LIBMAGIC */
  return;
}
static enum MHD_Result generate_page1 (void *cls,
    struct MHD_Connection *connection, const char *url, const char *method,
    const char *version, const char *upload_data, size_t *upload_data_size,
    void **ptr)
{
  struct MHD_Response *response;
  enum MHD_Result ret;
  int fd, buf_size = 2048;
  struct stat buf;
  struct ts_httpd *ts = cls;
  http_cal cal = ts->callback; 

  //printf("[++++]F%s L%d method:%s, url:%s \n", __FILE__, __LINE__, method, url);
  //printf("\tL%d data:%s[%ld]\n", __LINE__, upload_data, *upload_data_size);
  char *res = NULL;
  if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) {
    if (!(*ptr)) {
      *ptr = calloc(1, buf_size);
      return MHD_YES;
    }
    if (0 != *upload_data_size) {
      if (*upload_data_size > buf_size) {
        printf("[ERROR]F%s L%d \
            The message is too long(%zu) \
            and the default storage size(%d) needs to be increased\n",
            __FILE__, __LINE__, *upload_data_size, buf_size);
      }
      strcpy(*ptr, upload_data);
      *upload_data_size = 0;
      return MHD_YES;//1
    } else {
      res = cal(method, url, *ptr, ts->user_data);
      //free(*ptr);
    }
  } else if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) {
    res = cal(method, url, NULL, ts->user_data);
  }
  if (res) {
    response = MHD_create_response_from_buffer(
        strlen(res), res, MHD_RESPMEM_MUST_COPY);
    free(res);
    if (!response) {
      printf("[ERROR]F%s L%d RESPONSE IS NULL\n", __FILE__, __LINE__);
      exit(1);
    }
    ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    MHD_destroy_response (response);
    //printf("[DEBUG]F%s L%d connection:%p response:%p\n", __FILE__, __LINE__,
     //   connection, response);
    //printf("[DEBUG]F%s L%d ret:%d\n", __FILE__, __LINE__, ret);
    return ret;
  }
  if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) {
#ifdef MHD_HAVE_LIBMAGIC
    char file_data[MAGIC_HEADER_SIZE];
    ssize_t got;
#endif /* MHD_HAVE_LIBMAGIC */
    const char *mime;
    char filename[1024], *index_path = "/index.html";
    const char *url_path;
    if ( (0 != strcmp (method, MHD_HTTP_METHOD_GET)) &&
         (0 != strcmp (method, MHD_HTTP_METHOD_HEAD)) )
      return MHD_NO;    /* unexpected method (we're not polite...) */
    fd = -1;
    if (!strcmp(url, "/")) {
      url_path = index_path;
    } else {
      url_path = url;
    }
    if (ts->root_path) {
      snprintf(filename, sizeof(filename), "%s%s", ts->root_path, url_path);
      printf(" ROOT PATH:%s\n", ts->root_path);
    } else {
      printf("[ERROR] NO ROOT PATH\n");
      exit(1);
    }
    fd = open(filename, O_RDONLY);
    //fd = open (&url[1], O_RDONLY);
    if ( (-1 != fd) && ( (0 != fstat (fd, &buf)) ||
           (! S_ISREG (buf.st_mode)) ) ) {
      (void) close (fd);
      fd = -1;
    }
    if (-1 == fd) {
      printf("[ERROR] NO SUCH FILE:%s\n", filename);
      return MHD_queue_response (connection, MHD_HTTP_NOT_FOUND,
                                 file_not_found_response);
    }
#ifdef MHD_HAVE_LIBMAGIC
    got = read (fd, file_data, sizeof (file_data));
    (void) lseek (fd, 0, SEEK_SET);
    if (-1 != got)
      mime = magic_buffer (magic, file_data, got);
    else
#endif /* MHD_HAVE_LIBMAGIC */
    mime = NULL;
    if (NULL == (response = MHD_create_response_from_fd (buf.st_size, fd))) {
      (void) close (fd);
      return MHD_NO;
    }
    if (NULL != mime)
      (void) MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE,
                                      mime);
    ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    if (ret == MHD_NO)
      return ret;
    MHD_destroy_response (response);
    return ret;
  }
  return MHD_queue_response (connection, MHD_HTTP_FORBIDDEN,
      request_refused_response);
}
void *httpd_start1(int port, void *callback, const char *path, void *user_data)
{
  struct MHD_Daemon *d;
#ifdef MHD_HAVE_LIBMAGIC
  magic = magic_open (MAGIC_MIME_TYPE);
  (void) magic_load (magic, NULL);
#endif /* MHD_HAVE_LIBMAGIC */
  struct ts_httpd *ts = calloc(1, sizeof(*ts));
  ts->callback = callback;
  ts->root_path = path;
  ts->user_data = user_data;
  printf("%s L%d f-%s root_path:%s\n", __FILE__, __LINE__, __func__,
      ts->root_path);

  d = MHD_start_daemon (
      MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD| MHD_USE_ERROR_LOG,
      port, NULL, NULL,
      &generate_page1, ts,
      MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t)(256*1024),
      MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int)(64),
      MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) (1200),
      MHD_OPTION_THREAD_POOL_SIZE, (unsigned int)32,
      MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
      MHD_OPTION_END);
  return d;
}
struct Request {
  char *data;
};
static void request_completed_callback2 (void *cls,
    struct MHD_Connection *connection, void **con_cls,
    enum MHD_RequestTerminationCode toe)
{
  struct Request *request = *con_cls;
  if (request) {
    if (request->data)
      free(request->data);
    free(request);
  }
}
static enum MHD_Result generate_page2 (void *cls,
    struct MHD_Connection *connection, const char *url, const char *method,
    const char *version, const char *upload_data, size_t *upload_data_size,
    void **ptr)
{
  struct MHD_Response *response;
  enum MHD_Result ret;
  int fd;
  struct stat buf;
  struct ts_httpd *ts = cls;
  http_cal cal = ts->callback; 
  struct Request *request = NULL;

  //printf("[++++]F%s L%d method:%s, url:%s \n", __FILE__, __LINE__, method, url);
  //printf("\tL%d data:%s[%ld]\n", __LINE__, upload_data, *upload_data_size);
  char *res = NULL;
  if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) {
    if (!(*ptr)) {
      *ptr = request = calloc(1, sizeof(*request));
      if (!request) {
        return MHD_NO;
      }
      return MHD_YES;
    }
    request = *ptr;
    if (0 != *upload_data_size) {
      request->data = calloc(1, *upload_data_size);
      strcpy(request->data, upload_data);
      *upload_data_size = 0;
      return MHD_YES;//1
    } else {
      res = cal(method, url, request->data, ts->user_data);
    }
  } else if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) {
    res = cal(method, url, NULL, ts->user_data);
  }
  if (res) {
    response = MHD_create_response_from_buffer(
        strlen(res), res, MHD_RESPMEM_MUST_COPY);
    free(res);
    if (!response) {
      printf("[ERROR]F%s L%d RESPONSE IS NULL\n", __FILE__, __LINE__);
      exit(1);
    }
    ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    MHD_destroy_response (response);
    //printf("[DEBUG]F%s L%d connection:%p response:%p\n", __FILE__, __LINE__,
     //   connection, response);
    //printf("[DEBUG]F%s L%d ret:%d\n", __FILE__, __LINE__, ret);
    return ret;
  }
  if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) {
#ifdef MHD_HAVE_LIBMAGIC
    char file_data[MAGIC_HEADER_SIZE];
    ssize_t got;
#endif /* MHD_HAVE_LIBMAGIC */
    const char *mime;
    uid_t uid = getuid();
    struct passwd *pw = getpwuid(uid);
    char filename[1024], *index_path = "/index.html";
    const char *url_path;
    if ( (0 != strcmp (method, MHD_HTTP_METHOD_GET)) &&
         (0 != strcmp (method, MHD_HTTP_METHOD_HEAD)) )
      return MHD_NO;    /* unexpected method (we're not polite...) */
    fd = -1;
    if (!strcmp(url, "/")) {
      url_path = index_path;
    } else {
      url_path = url;
    }
    if (ts->root_path) {
      snprintf(filename, sizeof(filename), "%s/%s%s",
          pw->pw_dir, ts->root_path, url_path);
    } else {
      snprintf(filename, sizeof(filename), "%s/ts/html%s",
          pw->pw_dir, url_path);
    }
    fd = open(filename, O_RDONLY);
    //fd = open (&url[1], O_RDONLY);
    if ( (-1 != fd) && ( (0 != fstat (fd, &buf)) ||
           (! S_ISREG (buf.st_mode)) ) ) {
      (void) close (fd);
      fd = -1;
    }
    if (-1 == fd) {
      printf("[ERROR] NO SUCH FILE:%s\n", filename);
      return MHD_queue_response (connection, MHD_HTTP_NOT_FOUND,
                                 file_not_found_response);
    }
#ifdef MHD_HAVE_LIBMAGIC
    got = read (fd, file_data, sizeof (file_data));
    (void) lseek (fd, 0, SEEK_SET);
    if (-1 != got)
      mime = magic_buffer (magic, file_data, got);
    else
#endif /* MHD_HAVE_LIBMAGIC */
    mime = NULL;
    if (NULL == (response = MHD_create_response_from_fd (buf.st_size, fd))) {
      (void) close (fd);
      return MHD_NO;
    }
    if (NULL != mime)
      (void) MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE,
                                      mime);
    ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    if (ret == MHD_NO)
      return ret;
    MHD_destroy_response (response);
    return ret;
  }
  return MHD_queue_response (connection, MHD_HTTP_FORBIDDEN,
      request_refused_response);
}
void *httpd_start2(int port, void *callback, const char *path, void *user_data)
{
  struct MHD_Daemon *d;
#ifdef MHD_HAVE_LIBMAGIC
  magic = magic_open (MAGIC_MIME_TYPE);
  (void) magic_load (magic, NULL);
#endif /* MHD_HAVE_LIBMAGIC */
  struct ts_httpd *ts = calloc(1, sizeof(*ts));
  ts->callback = callback;
  ts->root_path = path;
  ts->user_data = user_data;

  d = MHD_start_daemon (
      MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD| MHD_USE_ERROR_LOG,
      port, NULL, NULL,
      &generate_page2, ts,
      MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t)(256*1024),
      MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int)(64),
      MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) (120),
      MHD_OPTION_THREAD_POOL_SIZE, (unsigned int)32,
      MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback2, NULL,
      MHD_OPTION_END);
  return d;
}
